/*
 * Copyright 2011 Dominik Pretzsch <dominik.pretzsch at gmail.com>.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package de.blacksheep.jmoteextract.archive.callback;

import de.blacksheep.jmoteextract.archive.Archive;
import de.blacksheep.jmoteextract.progress.ProgressUpdate;
import de.blacksheep.jmoteextract.progress.ProgressUpdateQueue;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.sf.sevenzipjbinding.ExtractAskMode;
import net.sf.sevenzipjbinding.ExtractOperationResult;
import net.sf.sevenzipjbinding.IArchiveExtractCallback;
import net.sf.sevenzipjbinding.ICryptoGetTextPassword;
import net.sf.sevenzipjbinding.ISequentialOutStream;
import net.sf.sevenzipjbinding.ISevenZipInArchive;
import net.sf.sevenzipjbinding.PropID;
import net.sf.sevenzipjbinding.SevenZipException;

/**
 * <code>ArchiveExtractCallback</code> is provided to the actual <br>
 * SevenZip-J-Binding <code>extract()</code> method and acts as <br>
 * a callback while the extraction process is running. <br>
 * <br>
 * A <code>PipedWriter</code> can be provided to this class for <br>
 * inter-thread communication. Therefor a special <br>
 * protocol is used: <br>
 * <br>
 * <table>
 * <tr><td> "T:Integer"        </td><td> The total amount of data to be processed    </td></tr>
 * <tr><td> "I:Integer|String" </td><td> The last processed item which was completed </td></tr>
 * <tr><td>                    </td><td> Integer: The hash value of the item         </td></tr>
 * <tr><td>                    </td><td> String: The name of the item                </td></tr>
 * <tr><td> "C:Integer"        </td><td> The overall amount of processed data in percent </td></tr>
 * <tr><td> "E:String"         </td><td> An error message                            </td></tr>
 * </table>
 * <br>
 * Each value is written as a line to the <code>PipedWriter</code>.
 * 
 * @author Dominik Pretzsch <dominik.pretzsch at gmail.com>
 */
public class ArchiveExtractCallback implements IArchiveExtractCallback, 
                                               ICryptoGetTextPassword {
    
    private Archive archive;
    private ProgressUpdateQueue progressUpdateQueue;
    private int hash = 0;
    private int index;
    private int numItems;    
    private File[] items = null;   
    
    private static final Logger LOGGER =  Logger.getLogger(ArchiveExtractCallback.class.getName());
    
    public ArchiveExtractCallback(Archive archive) {               
        this.archive = archive;
        this.progressUpdateQueue = ProgressUpdateQueue.getInstance();       
        LOGGER.log(Level.FINE, "ArchiveExtractCallback created for {0}", archive.getHash());
    }
    
    public void setNumberOfItems(int numItems) {
        this.numItems = numItems;
    }
    
    public int getNumberOfItems() {
        return numItems;
    }

    /**
     * Returns a <code>ISequentialOutStream</code> for the current item.<br>
     * If test mode is active, this method will always return null.
     * 
     * @param index           The index of the item to be processed
     * @param extractAskMode  The requested mode for the indexed item.
     * @return null           When item is not to be extracted or in test mode.
     * @return ISequentialOutStream  A Stream to address the indexed item.
     * @throws SevenZipException 
     * @see ArchiveExtract#TEST_MODE
     */
    public ISequentialOutStream getStream(final int index, 
                                          ExtractAskMode extractAskMode)
            throws SevenZipException {
        
        LOGGER.log(Level.FINE, "Try to getStream({0}, {1})...", new Object[]{index, extractAskMode.ordinal()});
                                              
        this.index = index;        
        if (extractAskMode != ExtractAskMode.EXTRACT) {
            return null;
        }
        
        if (items == null) {
            items = new File[numItems];
        }
        
        LOGGER.log(Level.FINE, "Returning ISequentialOutStream()");        
        return new ISequentialOutStream() {
            public int write(byte[] data) 
                       throws SevenZipException {             

                FileOutputStream fos = null;
                ISevenZipInArchive inArchive = archive.getInArchive();
                String extractionPath = archive.getAbsoluteExtractionPath();
                try {
                    File f = null;
                    if (items[index] == null) {
                        String filePath = (String) inArchive.getProperty(index, PropID.PATH);
                        String extPath = extractionPath.concat("\\").concat(filePath);

                        f = items[index] = new File(extPath);
                        if (f.exists()) {
                            f.delete();                            
                        }
                        f.createNewFile();
                    } else {
                        f = items[index];
                    }             
                    fos = new FileOutputStream(f, true);
                    fos.write(data);
                } catch (IOException ex) {
                    LOGGER.log(Level.SEVERE, null, ex);
                } finally {
                    if (fos != null) {
                        try {
                            fos.flush();
                            fos.close();
                        } catch (IOException ex) {
                            LOGGER.log(Level.SEVERE, null, ex);
                        }
                    }
                }

                hash ^= Arrays.hashCode(data);
                return data.length; // Return amount of proceed data
            }
        };
    }

    /**
     * Prepares operation on the archive
     * 
     * @param extractAskMode    The requested mode.
     * @throws SevenZipException 
     */
    public void prepareOperation(ExtractAskMode extractAskMode) 
            throws SevenZipException {
        
        LOGGER.log(Level.FINE, "Try to prepareOperation()...");
    }

    /**
     * Is called when an operation on a particular item has finished.<br>
     * It writes the result to the <code>PipedWriter</code>.
     * 
     * @param extractOperationResult    The result of the operation.
     * @throws SevenZipException 
     */
    public void setOperationResult(ExtractOperationResult extractOperationResult)
            throws SevenZipException {
        
        LOGGER.log(Level.FINE, "Try to setOperationResult({0})...", extractOperationResult);
        
        int archiveHash = archive.getHash();
        ISevenZipInArchive inArchive = archive.getInArchive();
                    
        if (extractOperationResult != ExtractOperationResult.OK) {
            LOGGER.log(Level.FINE, "setOperationResult() result: not OK");            
            progressUpdateQueue.enqueue(
                    ProgressUpdate.TYPE_ERROR_MSG, 
                    new Object[] {
                        archiveHash,
                        "Extraction error"
                    });
        } else {
            LOGGER.log(Level.FINE, "setOperationResult() result: OK");
            LOGGER.log(Level.FINE, "setOperationResult() hash: {0}, path: {1}", new Object[] { hash, (String) inArchive.getProperty(index, PropID.PATH)});            
            progressUpdateQueue.enqueue(
                    ProgressUpdate.TYPE_LAST_ITEM, 
                    new Object[] {
                        archiveHash,
                        hash,
                        inArchive.getProperty(index, PropID.PATH)
                    });            
            hash = 0;
        }
    }

    /**
     * Sets the total amount of data to be processed.<br>
     * It writes the total amount to the <code>PipedWriter</code>.
     * 
     * @param total     Total amount of data to be processed.
     * @throws SevenZipException 
     */
    public void setTotal(long total)
            throws SevenZipException {
        
        LOGGER.log(Level.FINE, "Try to setTotal({0})...", total);
        
        int archiveHash = archive.getHash();
        
        progressUpdateQueue.enqueue(
                ProgressUpdate.TYPE_TOTAL_BYTES,
                new Object[]{
                    archiveHash,
                    total
                });
    }

    /**
     * Is regularly called to set the actual processed amount <br>
     * of data. It writes the actual processed amount as percent <br>
     * to the <code>PipedWriter</code>.
     * 
     * @param completeValue
     * @throws SevenZipException 
     */
    public void setCompleted(long completeValue)
            throws SevenZipException {
        
        LOGGER.log(Level.FINE, "Try to setCompleted({0})...", completeValue);
        
        int archiveHash = archive.getHash();
        
        progressUpdateQueue.enqueue(
                ProgressUpdate.TYPE_COMPLETE_BYTES,
                new Object[]{
                    archiveHash,
                    completeValue
                });
    }

    /**
     * This method is called whenever a password is neccessary <br>
     * to proceed.
     * 
     * @return
     * @throws SevenZipException 
     */
    public String cryptoGetTextPassword() 
            throws SevenZipException {
        
        LOGGER.log(Level.FINE, "Try to cryptoGetTextPassword()...");        
        throw new UnsupportedOperationException("Password protected archives are not supported yet.");
        //return null;
    }
}
